import 'dart:io';

import 'package:args/command_runner.dart';
import 'package:frotools/src/scripts/nm_script.dart';

const libName = 'generateLibs';

const argInput = 'input';
const argPrefix = 'prefix';
const argFramework = 'libs';
const argOldPrefix = 'old';

/// 1. 替换类名
class CodeFixCommand extends Command {
  NmScript script;
  String prefix;
  String oldPrefix;
  List<DirModel> dirs = [];

  @override
  String get description => '代码混淆重定义';

  @override
  String get name => 'redefine';

  CodeFixCommand() {
    argParser.addOption(
      argPrefix,
      abbr: 'p',
      help: '需要混淆文件前缀',
    );

    argParser.addOption(
      argOldPrefix,
      abbr: 'o',
      help: '旧仓库的前缀(为了兼容字符串的替换！)',
    );

    argParser.addOption(
      argInput,
      abbr: 'i',
      help: '需要混淆库的目录',
      valueHelp: 'dir',
    );

    argParser.addOption(
      argFramework,
      abbr: 'f',
      help: '检测标记符的framework',
      valueHelp: 'libXXX.a',
    );
  }

  @override
  Future run() async {
    // 获取新文件的前缀
    if (argResults.wasParsed(argPrefix)) {
      prefix = argResults[argPrefix];
    }

    // 旧仓库的前缀
    if (argResults.wasParsed(argOldPrefix)) {
      oldPrefix = argResults[argOldPrefix];
    }

    // 需要校验framework的路径。
    if (argResults.wasParsed(argFramework)) {
      script = await NmScript(path: argResults[argFramework]).useFactory();
    }

    // 源代码的根目录
    if (argResults.wasParsed(argInput)) {
      dirs.clear();

      var iptPath = argResults[argInput];
      var paths = await getAllLocalFils(iptPath);

      paths.forEach((path) {
        print('working:$path');

        // 替换前的文件名 file.h
        var fileName = (path as String).split('/').last;
        // 替换前的文件的全路径
        var absoluteDir = (path as String).replaceAll(fileName, '');
        // 替换前的文件的相对路径-基于 -p 目录下 /xxx/file.h
        var current = (path as String).split(iptPath).last;
        // 替换前的文件名 file.h
        var rplF = getCategoryName(fileName, prefix);

        // 替换后文件的全路径，去掉文件 /xxx/yyy
        var dirReg = RegExp('/\$');
        var t1 = dirReg.hasMatch(iptPath) ? '' : '/';
        var t2 = current.replaceAll(fileName, '');
        var replaceDir = iptPath + t1 + libName + t2;
        replaceDir = replaceDir + (dirReg.hasMatch(replaceDir) ? '' : '/');

        var m = DirModel(
          clsName: fileName.split('.').first,
          sourceDir: absoluteDir,
          sourceFilePath: path,
          replaceDir: replaceDir,
          replaceFilePath: replaceDir + rplF,
          clsNewName: rplF.split('.').first,
        );

        dirs.add(m);
      });

      /// 实现代码替换
      sourceLibsIterator();

      return Future.value();
    }

    printUsage();
    return Future.value();
  }

  /// 遍历需要混淆库中的所有内容。
  void sourceLibsIterator() async {
    /// 获取需要替换的 typedef
    var typedefArr = await getAllTypedef();

    /// 类名添加一些本地类名
    var clsArr = script.cls;
    dirs.forEach((cls) {
      if (!clsArr.contains(cls.clsName)) clsArr.add(cls.clsName);
    });

    /// 获取所有`ENUM`的 Key Value
    var enumsArr = await getAllENUM();

    /// 替换所有类名
    for (var el in dirs) {
      /// 源代码
      var contents = await getFileContents(el.sourceFilePath);

      /// 替换类名
      if (clsArr.isNotEmpty) {
        contents = replaceAllMatches(contents, clsArr);

        /// 针对分类进行兼容
        contents = replaceAllCategory(contents, clsArr, dirs, el);
      }

      /// 替换常量、静态函数、内联函数
      if (script.expConst.isNotEmpty) {
        contents = replaceAllMatches(contents, script.expConst);
      }

      /// 替换 typedef 变量
      if (typedefArr.isNotEmpty) {
        contents = replaceAllMatches(contents, typedefArr);
      }

      /// 替换 ENUMS
      if (enumsArr.isNotEmpty) contents = replaceAllMatches(contents, enumsArr);

      /// 替换指定正则的字符串
      contents = replaceAllStrings(contents);

      /// 保存到本地
      var dir = Directory(el.replaceDir);
      if (!await dir.exists()) await dir.create(recursive: true);
      await File(el.replaceFilePath).writeAsString(contents);
    }

    // 打印输出
    clsArr.forEach((element) => print('需要替换的类|元类|协议: $element'));
    script.expConst.forEach((element) => print('需要替换的常量|静态方法|内联函数: $element'));
    typedefArr.forEach((element) => print('需要替换的typedef: $element'));
    enumsArr.forEach((element) => print('需要替换的枚举: $element'));

    var allStr = await getAllString();
    print('\nLib内所有的字符串：');
    allStr.forEach((element) => print('$element'));
  }

  /// 读取本地文件，把文件转换成数组返回
  Future<String> getFileContents(String path) async {
    var f = await File(path).readAsString();
    return f;
  }

  /// 按规则替换替换本地文件
  String replace(String content, String mthdt) {
    var reg = RegExp('([a-zA-Z\\d\\w\\+-]?)$mthdt([a-zA-Z\\d\\w\\+-]?)');

    if (!reg.hasMatch(content)) return content;

    var after = content.replaceAllMapped(reg, (match) {
      var g = match.group(0);
      if (g == mthdt) return '$prefix$g';
      return g;
    });

    return after;
  }

  String replaceAllCategory(
    String content,
    List mthdt,
    List<DirModel> models,
    DirModel el,
  ) {
    var after = content;

    /// 每个类导入分类方法
    /// #import "UIColor+SDHexString.h"
    for (var cls in mthdt) {
      var clsCode = '\"$cls.h\"';
      if (!content.contains(clsCode)) continue;

      var replace;

      for (var m in models) {
        if (m.clsName == cls) {
          replace = m;
          break;
        }
      }

      if ((replace as DirModel) != null) {
        after = after.replaceAll(cls, (replace as DirModel).clsNewName);
      }
    }

    /// 解决自己内中的类名
    if (el.clsName.contains('+')) {
      var regKeys = ['@interface', '@implementation'];

      for (var key in regKeys) {
        var clsArr = el.clsName.split('+');

        var reg =
            RegExp('$key([\\s]*)${clsArr.first}([\\s]*)\\(${clsArr.last}\\)');

        if (reg.hasMatch(after)) {
          after = after.replaceAllMapped(reg, (match) {
            var g = match.group(0);
            var oldCls = el.clsName.split('+').last;
            var newCls = el.clsNewName.split('+').last;

            g = g.replaceAll(oldCls, newCls);
            return g;
          });
        }
      }
    }

    /// 解决 NSObjct+AAA，变成其他类的方式=> NSObjct_AAA
    var conArr = after.split('\n');
    var tmpt = <String>[];

    for (var line in conArr) {
      for (var cls in mthdt) {
        if (cls.contains('+')) {
          var clsArr = (cls as String).split('+');
          if (line.contains(clsArr.first) && line.contains(clsArr.last)) {
            if (!line.contains(prefix)) {
              line = line.replaceAll(clsArr.last, prefix + clsArr.last);
            }
          }
        }
      }
      tmpt.add(line);
    }
    after = tmpt.join('\n');
    return after;
  }

  String replaceAllMatches(String content, List mthdt) {
    var after = content;

    for (var mth in mthdt) {
      after = replace(after, mth);
    }

    return after;
  }

  /// 获取源代码中所有的 typedef 类型
  Future<List<String>> getAllTypedef() async {
    var typedefArr = <String>[];

    for (var el in dirs) {
      /// 源代码
      var content = await getFileContents(el.sourceFilePath);

      var reg = RegExp(
          'typedef ([\\d\\w\\s.\\*[a-zA-Z])+\\((\\s)*\\^(\\s)*([\\d\\w\\s.\\*[a-zA-Z])+\\)');

      if (!reg.hasMatch(content)) continue;

      content.replaceAllMapped(reg, (match) {
        var g = match.group(0);
        g = g.replaceAll(' ', '');
        g = g.split('^').last;
        g = g.replaceAll(')', '');
        if (!typedefArr.contains(g)) typedefArr.add(g);
        return g;
      });
    }

    return typedefArr;
  }

  Future<List<String>> getAllString() async {
    var strArr = <String>[];

    for (var el in dirs) {
      /// 源代码
      var content = await getFileContents(el.sourceFilePath);

      var reg = RegExp('@"().+"');

      if (!reg.hasMatch(content)) continue;

      content.replaceAllMapped(reg, (match) {
        var g = match.group(0);
        strArr.add(g);
        return g;
      });
    }

    return strArr;
  }

  /// 查询所有 `ENUM`
  Future<List<String>> getAllENUM() async {
    var strArr = <DirModel>[];

    var reg1 =
        RegExp('typedef ([\\s]*)NS_ENUM([\\s]*)\\(([a-zA-Z,\\s\\d\\w_-]*)\\)');
    var reg1_1 = RegExp(
        'typedef ([\\s]*)NS_OPTIONS([\\s]*)\\(([a-zA-Z,\\s\\d\\w_-]*)\\)');

    var reg2 = RegExp('typedef ([\\s]*)enum([\\s:a-zA-Z\\d\\w]*){');

    /// 1. 先获取所有存在`ENUM`的文件
    for (var el in dirs) {
      /// 源代码
      var content = await getFileContents(el.sourceFilePath);

      if (reg1.hasMatch(content) ||
          reg1_1.hasMatch(content) ||
          reg2.hasMatch(content)) {
        if (!strArr.contains(el.clsName)) strArr.add(el);
      }
    }

    /// 把含有`ENUM`的文件数组化，定位到只有`ENUM`的代码块。
    var allEnums = <String>[];

    for (var dir in strArr) {
      var content = await File(dir.sourceFilePath).readAsString();

      var conArr = content.split('\n');

      var canPrint = false;
      var isnum = false; // typedef enum
      var isStart = false;

      for (var line in conArr) {
        var lineNoSpace = line.replaceAll(' ', '');

        if (reg1.hasMatch(line) || reg1_1.hasMatch(line)) {
          canPrint = true;

          lineNoSpace = lineNoSpace.split(',').last;
          lineNoSpace = lineNoSpace.split(')').first;

          if (!allEnums.contains(lineNoSpace)) allEnums.add(lineNoSpace);
        }

        if (reg2.hasMatch(line)) {
          isnum = true;
          canPrint = true;
        }

        if (canPrint) {
          if (!lineNoSpace.contains('{') && !lineNoSpace.contains('}')) {
            var canAdd = true;

            /// 判断是否以`/*`开头,如果是则需要找到结尾`*/`
            if (line.contains(RegExp('^([\\s]*)/\\*'))) {
              isStart = true;
            }

            if (line.contains(RegExp('(.*)\\*/\$'))) {
              canAdd = false;
              isStart = false;
            }

            if (isStart) canAdd = false;

            if (lineNoSpace.contains(RegExp('^([\s]*)//'))) canAdd = false;

            if (canAdd) {
              lineNoSpace = lineNoSpace.split('/').first;
              lineNoSpace = lineNoSpace.split('=').first;
              lineNoSpace = lineNoSpace.replaceAll(',', '');
              allEnums.add(lineNoSpace);
            }
          }
        }

        if (line.contains('}')) {
          if (isnum) {
            lineNoSpace = lineNoSpace.replaceAll('}', '');
            lineNoSpace = lineNoSpace.replaceAll(';', '');

            if (!allEnums.contains(lineNoSpace)) allEnums.add(lineNoSpace);
          }

          canPrint = false;
          isnum = false;
        }
      }
    }

    // 过滤重复项
    var tmpt = <String>[];
    for (var em in allEnums) {
      var canAdd = true;
      for (var t in tmpt) {
        if (em == t) canAdd = false;
      }
      if (canAdd && em.isNotEmpty) tmpt.add(em);
    }

    return tmpt;
  }

  /// 针对分类进行修改
  /// NSBezierPath+SDRoundedCorners.m
  String getCategoryName(String old, String prefix) {
    if (!old.contains('+')) return prefix + old;

    var arr = old.split('+');
    return arr.first + '+' + prefix + arr.last;
  }

  Future<List> getAllLocalFils(String path) async {
    var fileArr = [];
    try {
      var directory = Directory(path);
      var files = directory.listSync();
      for (var f in files) {
        var bool = FileSystemEntity.isFileSync(f.path);

        if (bool) {
          if (FileSystemEntity.isLinkSync(f.path)) continue;

          var type = f.path.split('.').last;
          if (type == 'h' || type == 'm') fileArr.add(f.path);
        } else {
          if (f.path.split('/').last == libName) continue;
          var ret = await getAllLocalFils(f.path);
          fileArr.addAll(ret);
        }
      }
    } catch (e) {
      print('');
    }
    return fileArr;
  }

  String replaceAllStrings(String content) {
    if (oldPrefix.isEmpty) return content;

    var reg = RegExp('@"().+"');

    if (!reg.hasMatch(content)) return content;

    content = content.replaceAllMapped(reg, (match) {
      var g = match.group(0);
      if (g.contains(prefix + oldPrefix)) return g;
      g = g.replaceAll(oldPrefix, prefix + oldPrefix);
      return g;
    });

    return content;
  }

  /// 获取文件中所有文件的前缀
  void getSysLibsPrefix(String path) async {
    var f = await File(path).readAsString();
    var fArr = f.split('\n');

    var reg = RegExp('([a-zA-Z\\w\\d\\s\\+-]*).h\$');
    var regChr = RegExp('^([A-Z]){2}');

    var preArr = <String>[];

    for (var i in fArr) {
      if (reg.hasMatch(i) && !i.contains('Swift')) {
        var g = reg.firstMatch(i).group(0);
        if (regChr.hasMatch(g)) {
          var c = regChr.firstMatch(g).group(0);
          if (!preArr.contains(c)) preArr.add(c);
        }
      }
    }

    preArr.forEach((element) => print(element));
  }
}

class DirModel {
  final String sourceDir;
  final String sourceFilePath;
  final String replaceDir;
  final String replaceFilePath;
  final String clsName;
  final String clsNewName;

  DirModel({
    this.sourceDir,
    this.sourceFilePath,
    this.replaceDir,
    this.replaceFilePath,
    this.clsName,
    this.clsNewName,
  });

  @override
  String toString() => 'sourceDir: $sourceDir\n'
      'sourceFilePath: $sourceFilePath\n'
      'replaceDir: $replaceDir\n'
      'replaceFilePath: $replaceFilePath\n'
      'clsNewName: $clsNewName\n'
      'clsName: $clsName\n';
}
